from Build import KG
from CsynToId import transFuncMap
from Query import query, InferenceMachine
from Constant import (DB, PARTTEN, CON, INS, RULE, REL,
                                  ATTR, BUILD, PULL, DEL, ADD, NULL,
                                  CHILDEREN, PARENTS, ID, BODY, UNKNOWN,
                                  CSYN, ESYN, GLOSS)

import re
from functools import reduce
from operator import add
from collections import deque, Iterable



KEYWORD = ('概念', '实例', '关系', '属性', '规则', 'name', ATTR, 'child', 'ins', GLOSS)
SPECIALREL = (CHILDEREN, PARENTS, ATTR, CSYN, ESYN, GLOSS)

SPECIALCON = (CON, ATTR, REL, INS, RULE)


SPLIT = ' '
CONTERM, RELTERM = 'ConTerm', 'RelTerm'
CommentSymbol = '#'


P_OP = re.compile(r'(\+=)|(-=)|(=)|(\+)|(-)')
P_KW = re.compile(r'(\.child)|(\.name)|(\.性质)|(\.ins)|(\.)|'
                  r'(实例)|(概念)|(关系)|(属性)|(规则)|(&)')

zhPattern = re.compile(u'[\u4e00-\u9fa5]+')

is_chinese = lambda term: bool(zhPattern.findall(term))
is_unknown = lambda term: re.sub(r'([\d]+)', '', term).isupper()


# 移除已存在的概念
def pull_exist_term(db, prop):
    def decorator(func):
        if prop in ['con', 'attr']:
            get_id = db.cname2id
        else:
            get_id = db.rname2id

        def wrapper(terms):
            terms = [term for term in terms if not get_id(term)]
            return func(terms)

        return wrapper

    return decorator

def matching(content, split=SPLIT):
    '''
    str : ( xxx ,  xxx  ...)/xxx -> list : [xxx,xxx]/[xxx]
    :return list or str:
    '''

    if not split or split not in content:
        return [content.strip().lstrip('(').rstrip(')')]

    for res in PARTTEN.findall(content):
        # 如果不是空格 那么就是匹配到的内容
        if not res.isspace():
            return [i.strip() for i in res.split(split) if (not i.isspace() and i)]

def get_kw(seq):
    """得到序列中的关键字"""
    res = P_KW.search(seq)
    return res.group() if res else '.'

def get_op(seq):
    """得到序列中的操作符"""
    res = P_OP.search(seq)
    return res.group() if res else ''

class Interpreter:
    def __init__(self, DBName, transFuncMap, execFuncMap=None):
        self.DBName = DBName
        self.DB = DB(DBName)
        self.KG = KG(DBName)
        self.varSpace = {} # 变量空间

        self.transFuncMap = transFuncMap
        self.funcMap = {
            "概念": self.find_all_con,
            "实例": self.find_all_ins,
            "属性": self.find_all_attr,
            "关系": self.find_all_rel,
            "规则": self.find_all_rule,
            '概念-=': self.del_con,
            '概念+=': self.add_con,
            '实例-=': self.del_ins,
            '实例+=': self.add_ins,
            '关系-=': self.del_rel,
            '关系+=': self.add_rel,
            '属性-=': self.del_attr,
            '属性+=': self.add_attr,
            '规则-=': self.del_rule,
            '规则+=': self.add_rule,
            ATTR+'=': self.upd_property,
            GLOSS+'=':self.upd_term_glosee,
            'child-=': self.del_child,
            'child+=': self.add_child,
            'ins-=': self.del_child,
            'ins+=': self.add_child,
            'name+=': self.add_name,
            'name-=': self.del_name,
            'name=': self.set_name,
            '+': self.create_conn,
            '-': self.break_conn,
            '-=': self.pull_rel_value,
            '+=': self.add_rel_value,
            '&=': self.add_var,
            'query': self._query,
            'multiple_queries': self.multiple_queries,
             'reasoning': self.reasoning
        }

    def parse_parms(self, parms):
        '''
        用于参数解析 预处理
        :param param  str:(x1,x2,x3)/x1/var
        :return list :
        '''

        if self.varSpace.get(parms):
            return self.varSpace[parms]
        else:
            return matching(parms)

    def parse_left(self, left):
        """解析序列的左侧"""
        left, *right = left.split('.')
        left = left.strip()
        if right:
            right = right[0].strip()

        if left in KEYWORD:
            keyWord, arg = left, []
        elif right in KEYWORD:
            keyWord, arg = right, self.parse_parms(left)
        elif '&' in left:
            varName = left.strip()
            keyWord, arg = '&', [varName]
        else:
            keyWord, arg = '', [self.parse_parms(left), self.parse_parms(right)]

        return keyWord, arg

    def parse_right(self, right):
        """解析序列的右侧"""
        if ':-' in right:
            return matching(right, None)[0]
        else:
            return self.parse_parms(right.strip())

    def query_to_triples(self, seq):
        """解析查询的参数"""
        if seq.count('.') == 0:
            func, arg = 'query', [seq.strip(), NULL, NULL]
        elif seq.count('.') == 1:

            func, arg = 'query', [i.strip() for i in seq.split('.')] + [NULL]
        else:

            arg = [(NULL if '?' in i else i.strip()) for i in seq.split('.')]

            if arg[1] != NULL and arg[1] not in SPECIALREL:
                relId = arg[1] = self.get_rel_id(arg[1])
                relAttr = self.DB.find_rel_by_id(relId)[ATTR][0]
                func = 'query' if relAttr != RULE else 'reasoning'
            else:
                func = 'query'

        return func, arg

    def parse_query_sequence(self, seq):
        """解析查询语句
        return 'query'/'multiple_queries'/'reasoning' and arg
        """
        func, arg = None, []
        # 特殊查询
        if seq in KEYWORD[:4]:
            func = seq
        # 多重查询
        elif seq.count('.') > 2:
            func = 'multiple_queries'
            arg = self.get_rule_body(seq)
        # 简单查询 / 推理
        else:
            func, arg = self.query_to_triples(seq)

        return func, arg

    def parse(self, seq):
        """解析语句
        return func arg"""
        op = get_op(seq)
        # 构建
        if op:
            left, right = seq.split(op)
            keyWord, leftArg = self.parse_left(left)
            rightArg = self.parse_right(right)
            arg = leftArg + [rightArg]

        # 查询
        else:
            keyWord, arg = self.parse_query_sequence(seq.strip())

        return keyWord + op, arg

    def exec(self, func, args):
        """执行单条语句"""
        return self.funcMap[func](*args)

    def can_exec(self, seq):
        """判断语句是否可执行"""
        return seq and CommentSymbol not in seq

    def run(self, seqs):
        count = 0
        rtn = []
        # with open('cknow', mode='r', encoding='utf-8') as code:
        #     seqs=code.read().split('//')[0].split('\n')
        for seq in seqs:
            if self.can_exec(seq):
                func, arg = self.parse(seq)
                res = self.exec(func, arg)
                if not isinstance(res, str) or 'query' in res:
                    rtn.append(res)
        return res

    def shell(self):
        print(self.transFuncMap)
        print('welcome to wlang')
        while True:
            seq = input('->')
            if seq == 'quit':
                break
            func, arg = self.parse(seq)
            if func in self.transFuncMap:
                arg = self.transFuncMap[func](self.DB, *arg)
            print(arg)
            if arg:
                res = self.exec(func, arg)
                if res:
                    print(res)
        print('bye bye~')

    def get_rule_body(self, body):
        """得到规则体"""

        body = deque(body.split('.'))
        res = []
        while body:
            con1 = body.popleft().strip()
            rel = body.popleft().strip()
            con2, *nextCon = [i.strip() for i in body.popleft().split(' ') if i]
            res.append([con1, rel, con2])
            if nextCon and not nextCon[0].isspace():
                body.appendleft(nextCon[0])
        return res


    def get_rel_id(self, name):
        """得到关系的id"""
        if name == NULL or (is_unknown(name) and (not is_chinese(name))) or name in SPECIALREL or name[1:].isdigit():
            id_ = name
        else:
            id_ = self.DB.rname2id(name)
            id_ = id_[0] if id_ else None

        return id_

    def get_con_id(self, name):
        """得到概念的id"""
        if name == NULL or (is_unknown(name) and (not is_chinese(name))) or name in SPECIALCON or name.isdigit():
            id_ = name
        else:
            id_ = self.DB.cname2id(name)
            id_ = id_[0] if id_ else None

        return id_

    def add_con(self, *consId):

        self.KG.add_con_or_ins(consId, [CON] * len(consId))


    def add_ins(self, *inssId):
        return self.KG.add_con_or_ins(inssId, [INS] * len(inssId))

    def add_attr(self, *attrsId):
        return self.KG.add_rel(attrsId, [ATTR] * len(attrsId))

    def add_rel(self, *relsId):
        return self.KG.add_rel(relsId, [REL] * len(relsId))

    def add_rule(self, rule):
        head, body = rule.split(':-')
        unknown1, name, unknown2 = head.split('.')
        unknown = [unknown1.strip(), unknown2.strip()]
        # res = matching(body, None)
        body = self.get_rule_body(body)

        # name -> id
        body = [self.get_triple_id(*triple) for triple in body]
        return self.KG.add_rule(name, unknown, body)

    def del_con(self, *consIds):
        for conId in consIds:
            self.KG.del_con(conId)


    def del_ins(self, *inssId):
        self.KG.del_ins(inssId)

    def del_attr(self, *attrsId):
        for attrId in attrsId:
            self.KG.del_rel(attrId)

    def del_rel(self, *relsId):
        for relId in relsId:
            self.KG.del_rel(relId)

    def del_rule(self, *rulesId):
        for rileId in rulesId:
            self.KG.del_rel(rileId)

    def create_conn(self, con1Id, relId, con2Id):
        self.KG.conn_cons(con1Id, relId, con2Id, BUILD)

    def break_conn(self, con1Id, relId, con2Id):
        self.KG.conn_cons(con1Id, relId, con2Id, DEL)

    def add_rel_value(self, con1Id, relId, con2Ids):
        triplesId = [(con1Id, relId, con2Id) for con2Id in con2Ids]
        self.KG.upd_conn(triplesId, [ADD] * len(triplesId))

    def pull_rel_value(self, con1Id, relId, con2Ids):
        triplesId = [(con1Id, relId, con2Id) for con2Id in con2Ids]
        self.KG.upd_conn(triplesId, [PULL] * len(triplesId))

    def add_child(self, parId, childIds):
        for childId in childIds:
            self.KG.add_child(parId, childId)

    def del_child(self, parId, childIds):
        for childId in childIds:
            self.KG.del_child(parId, childId)

    def set_name(self, termId, attr, newNames):
        func = self.KG.upd_con_name if attr in [CON, INS] else self.KG.upd_rel_name
        func(termId, newNames)

    def add_name(self, termId, attr, newNames):
        if attr in [CON, INS]:
            func_oldnames = self.DB.cid2name
            func_upd = self.KG.upd_con_name
        else:
            func_oldnames = self.DB.rid2name
            func_upd = self.KG.upd_rel_name
        oldnams = func_oldnames(termId)
        func_upd(termId, list(set(oldnams) | set(newNames)))


    def del_name(self, termId, attr, newNames):
        if attr in [CON, INS]:
            func_oldnames = self.DB.cid2name
            func_upd = self.KG.upd_con_name
        else:
            func_oldnames = self.DB.rid2name
            func_upd = self.KG.upd_rel_name
        oldnams = func_oldnames(termId)
        func_upd(termId, list(set(oldnams) - set(newNames)))

    def upd_property(self, termId, attr):
        if attr in [CON, INS]:
            upd_func = self.KG.upd_con_attr
        else:
            upd_func = self.KG.upd_rel_attr
        upd_func(termId, attr)

    def upd_term_glosee(self, termId, attr, gloss):
        if attr in [CON, INS]:
            upd_func = self.KG.upd_con_gloss
        else:
            upd_func = self.KG.upd_rel_gloss
        upd_func(termId, gloss)


    def add_var(self, varName, varValue):
        """添加变量"""
        self.varSpace[varName] = varValue
        return 'add var: {var} value: {value}'.format(var=varName, value=varValue)


    def get_triple_id(self, con1, rel, con2):
        """得到 三元组中每一元的id"""
        con1Id = self.get_con_id(con1)
        relId = self.get_rel_id(rel)
        con2Id = self.get_con_id(con2)
        return [con1Id, relId, con2Id]

    def _query(self, *triple):
        """查询：简单查询"""
        # ->id
        triple = self.get_triple_id(*triple)

        res = query(self.DB, *triple)
        if isinstance(res, bool):
            res = res
        elif isinstance(res, (list, set, tuple)):
            find_name = self.DB.rid2name if triple[1] == NULL else self.DB.cid2name

            res = [find_name(i) or [i] for i in res]
        else:
            find_rel_name = self.DB.rid2name
            find_con_name = self.DB.cid2name
            res ={(tuple(find_rel_name(rel)) if rel not in SPECIALREL else rel):
                            [(find_con_name(con) if rel not in (CSYN, ESYN, ATTR) else [con]) for con in cons]
                  for rel, cons in res.items()}


        return res
    def multiple_queries(self, *triples):
        """多重查询"""
        triples = [self.get_triple_id(*triple) for triple in triples]
        actions = InferenceMachine(self.DBName, triples).exec()
        # 建立属性索引
        attrIndex = {REL: [], CON: []}
        for triple in triples:
            con1, rel, con2 = triple
            if is_unknown(con1):
                attrIndex[CON].append(con1)
            if is_unknown(rel):
                attrIndex[REL].append(rel)
            if is_unknown(con2):
                attrIndex[CON].append(con2)

        res = []
        for actionId in actions:
            actionName = {}
            for unk, val in actionId.items():
                if unk in attrIndex[CON]:
                    actionName[unk] = self.DB.cid2name(val)
                else:
                    actionName[unk] = self.DB.rid2name(val)
            res.append(actionName)

        return res


    def reasoning(self, c1, rule, c2):
        """推理"""
        ruleDoc = self.DB.find_rel_by_id(rule)
        # 规则体 未知量
        body, unknown = ruleDoc[BODY], ruleDoc[UNKNOWN]
        # 已知量
        known = {}

        if not is_unknown(c1):
            known[unknown[0]] = [self.get_con_id(i) for i in self.parse_parms(c1)]
        if not is_unknown(c2):
            known[unknown[1]] = [self.get_rel_id(i) for i in self.parse_parms(c1)]

        actions = InferenceMachine(self.DBName, body, known).exec()


        if is_unknown(c1):
            return [self.DB.cid2name(con) for con in set([action[c1] for action in actions if action.get(c1)])]
        elif is_unknown(c2):
            return [self.DB.cid2name(con) for con in set([action[c2] for action in actions if action.get(c2)])]
        else:
            return bool(actions)

    def find_all_term(self, collection, term):
        """找到所有的term"""
        res = collection.find({ATTR: term}, projection={ID: True, CSYN: True})
        return list(res)

    def find_all_con(self):
        """找到所有的概念"""
        return self.find_all_term(self.DB.CONDATA, CON)

    def find_all_ins(self):
        """找到所有的实例"""
        return self.find_all_term(self.DB.CONDATA, INS)

    def find_all_rel(self):
        """找到所有的关系"""
        return self.find_all_term(self.DB.RELDATA, REL)

    def find_all_attr(self):
        """找到所有的属性"""
        return self.find_all_term(self.DB.RELDATA, ATTR)

    def find_all_rule(self):
        """找到所有的规则"""
        return self.find_all_term(self.DB.RELDATA, RULE)




if __name__ == '__main__':
    I = Interpreter('DBNAME', transFuncMap=transFuncMap)
    I.shell()